package cn.xinfei.xdecision.data.core.frame.listener;


import cn.xinfei.xdecision.common.model.datax.consts.VariableConst;
import cn.xinfei.xdecision.common.model.datax.enums.*;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataException;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataExceptionType;
import cn.xinfei.xdecision.common.utils.util.StringUtil;
import cn.xinfei.xdecision.data.core.frame.executors.collect.credit.ICreditCollect;
import cn.xinfei.xdecision.data.core.frame.strategy.credit.CreditStrategy;
import cn.xinfei.xdecision.data.core.frame.strategy.StrategyContext;
import cn.xinfei.xdecision.data.core.schduler.CustomerScheduledTaskRregistrar;
import cn.xinfei.xdecision.data.core.mq.VariableProducer;

import cn.xinfei.xdecision.data.core.util.VariableConvertUtil;
import cn.xinfei.xdecision.data.core.vo.credit.CreditChannelRouteRequest;
import cn.xinfei.xdecision.data.core.vo.credit.CreditStatusDTO;

import cn.xinfei.xdecision.data.core.vo.credit.CreditStatusRequest;
import cn.xinfei.xdecision.data.core.vo.credit.CreditTaskDTO;
import cn.xinfei.xdecision.model.VariableMessageVO;
import cn.xinfei.xdecision.model.VariableApiRequest;
import cn.xinfei.xdecision.model.VariableApiResponse;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Transaction;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.*;

@Slf4j
@Component
public class CreditQueryListener {

    @Autowired
    StrategyContext strategyContext;

    @Autowired
    ICreditCollect creditCollect;

    @Autowired
    VariableProducer variableProducer;

    @Autowired
    CustomerScheduledTaskRregistrar customScheduledTaskRegistrar;

    @Autowired
    ApplicationContext applicationContext;

    private static final String CRON = "*/5 * * * * ?";


    @Value("${xdecision.data.credit.retry_times}")
    private int creditTaskRetryTimes;
    @Value("${xdecision.data.credit.route_channel.default_channel}")
    private String creditDefaultChannel;
    private static TransmittableThreadLocal<Map<String, Integer>> TASK_COUNT = new TransmittableThreadLocal<>();

//    @Async
//    @EventListener
//    public void onCreditQueryEvent(CreditQueryEvent creditQueryEvent) {
//        CreditStatusDTO creditQueryEventData = creditQueryEvent.getData();
//        MDC.put(VariableConst.COLLECT_STAGE, VarCatTypeEnum.CREDIT_QUERY.name());
//        MDC.put(VariableConst.HANDLE_NAME, ExecutorSceneEnum.SCHEDULER.name());
//
//        log.info("requestId: {} varCodes: {} 开始查询人行报文!",
//                creditQueryEventData.getRequestId(),
//                JSONObject.toJSONString(creditQueryEventData.getVarCodes()));
//        creditQuery(creditQueryEventData);
//        MDC.clear();
//    }

//    @Async
//    @EventListener
//    @Retryable(value = Exception.class, maxAttempts = 2, backoff = @Backoff(delay = 2000, multiplier = 1.5))
//    public void onAutoStopTaskEvent(CreditQueryStopEvent creditQueryStopEvent) {
//        CreditTaskDTO statusDTO = creditQueryStopEvent.getData();
//        MDC.put(VariableConst.COLLECT_STAGE, VarCatTypeEnum.CREDIT_QUERY.name());
//        MDC.put(VariableConst.HANDLE_NAME, ExecutorSceneEnum.SCHEDULER.name());
//        log.info("requestId: {}  完成人行报文查询 任务自动停止!",
//                statusDTO.getRequestId());
//        customScheduledTaskRegistrar.removeTriggerTask(statusDTO.getRequestId());
//        MDC.clear();
//    }

//    public void creditQuery(CreditStatusDTO creditQueryEventData) {
//        CreditStrategy creditStrategy = (CreditStrategy) strategyContext.getStrategy(VariableStrategyType.CREDIT.name());
//        setThreadLocal(creditQueryEventData.getRequestId());
//        CreditQueryTask creditQueryTask = new CreditQueryTask(
//                creditQueryEventData.getRequestId(),
//                creditStrategy,
//                creditQueryEventData,
//                creditCollect,
//                variableProducer,
//                applicationContext,
//                creditTaskRetryTimes,
//                creditDefaultChannel
//
//        );
//        TtlRunnable ttlRunnable = TtlRunnable.get(creditQueryTask);
//        customScheduledTaskRegistrar.addTriggerTask(
//                creditQueryEventData.getRequestId(),
//                ttlRunnable,
//                triggerContext -> new CronTrigger(CRON).nextExecutionTime(triggerContext)
//        );
//
//    }


    @Data
    @RequiredArgsConstructor
    public static class CreditQueryTask implements Runnable {

        private final String requestId;
        private final CreditStrategy creditStrategy;
        private final CreditStatusDTO creditStatusDTO;
        private final ICreditCollect creditCollect;
        private final VariableProducer variableProducer;
        private final ApplicationContext applicationContext;
        private final int retryTimes;
        private final String defaultChannel;
        private volatile boolean isCanceled = false;

        @Override
        public void run() {
            MDC.put(VariableConst.TRACE_ID, requestId);
            MDC.put(VariableConst.HANDLE_NAME, ExecutorSceneEnum.SCHEDULER.name());
            MDC.put(VariableConst.COLLECT_STAGE, VarCatTypeEnum.CREDIT_QUERY.name());
            log.info("requestId = {} 开始查询征询人行报告拉取状态 id_card_number = {}, channel = {}",
                    requestId,
                    creditStatusDTO.getIdCardNumber(),
                    creditStatusDTO.getCreditChannel());
            CreditStatusRequest creditStatusRequest = VariableConvertUtil.fromCreditStatusDTO(creditStatusDTO);

            CreditTaskDTO creditTaskDTO = new CreditTaskDTO(requestId);
            Transaction catT = Cat.newTransaction(VarCatTypeEnum.CREDIT_QUERY.getCode(), VarCatTypeEnum.CREDIT_QUERY.getName());
            try {
                if (this.isCanceled) {
                    log.info("requsetId = {} 人行查询任务自动停止!", requestId);
                    this.isCanceled = true;
                    applicationContext.publishEvent(new CreditQueryStopEvent(
                            this, creditTaskDTO
                    ));
                } else {
                    TransmittableThreadLocal<Map<String, Integer>> threadLocal = getThreadLocal();
                    setThreadLocal(requestId);
                    if (threadLocal.get().get(requestId) > retryTimes) {
                        log.info("requsetId = {} 人行查询任务 重试次数已达上线，自动停止！", requestId);
                        this.isCanceled = true;
                        applicationContext.publishEvent(new CreditQueryStopEvent(
                                this, creditTaskDTO
                        ));
                        initThreadLocal(requestId);
                    } else {
                        JSONObject statusResponse = creditStrategy.status(creditStatusRequest, creditStatusDTO.getInputParams());
                        log.info("requestId = {} 征信路由渠道 = {}", requestId, creditStatusDTO.getCreditChannel());
                        String inquiryStatus = statusResponse.getString("inquiry_status");
                        String code = statusResponse.getString("code");
                        String message = statusResponse.getString("message");
                        if (CreditInquiryStatus.QUERY_SUCCESS.getCode().equals(inquiryStatus)) {
                            log.info("requsetId = {}, id_card_number = {},channel = {} 人行报告拉取成功!",
                                    requestId,
                                    creditStatusDTO.getIdCardNumber(),
                                    creditStatusDTO.getCreditChannel());

                            VariableApiRequest variableApiRequest = buildRequest(
                                    new HashSet<>(creditStatusDTO.getVarCodes()),
                                    creditStatusDTO.getRequestId(),
                                    creditStatusDTO
                            );
                            VariableApiResponse variableApiResponse = creditCollect.creditCollect(variableApiRequest);
                            if ("10000".equals(variableApiResponse.getCode())) {
                                String variablesResponse = variableApiResponse.getVariablesResponse();
                                VariableMessageVO messageVO = VariableMessageVO.builder()
                                        .data(variablesResponse)
                                        .deliveryType(MqDeliveryScene.CREDIT_VAR_RESULT.getCode())
                                        .build();
                                variableProducer.send(messageVO, creditStatusDTO.getRequestId(), MqDeliveryScene.CREDIT_VAR_RESULT.getCode());
                                log.info("requsetId = {}, id_card_number = {},channel = {} 人行变量计算成功！ response:{}",
                                        requestId,
                                        creditStatusRequest.getIdCardNumber(),
                                        creditStatusRequest.getCreditChannel(),
                                        variablesResponse);
                                this.isCanceled = true;
                                applicationContext.publishEvent(new CreditQueryStopEvent(
                                        this, creditTaskDTO
                                ));
                                initThreadLocal(requestId);
                                catT.setStatus(Transaction.SUCCESS);
                            } else {
                                log.info("requsetId = {}, id_card_number = {},channel = {} 人行变量计算失败！ response:{}",
                                        requestId,
                                        creditStatusRequest.getIdCardNumber(),
                                        creditStatusRequest.getCreditChannel(),
                                        JSONObject.toJSONString(variableApiResponse));
                                catT.setStatus(new XDecisionDataException(XDecisionDataExceptionType.DATA_CREDIT_VAR_CAL_ERROR));
                            }
                        } else if (CreditInquiryStatus.QUERY_FAIL.getCode().equals(inquiryStatus) ||
                                CreditInquiryStatus.OVERDUE.getCode().equals(inquiryStatus) ||
                                CreditInquiryStatus.EMPTY.getCode().equals(code)) {
                            CreditInquiryStatus status = EnumUtil.fromCode(CreditInquiryStatus.class, inquiryStatus);
                            log.info("requsetId = {}, id_card_number = {},channel = {} 人行报告未拉取成功，停止查询,status = {}",
                                    requestId,
                                    creditStatusDTO.getIdCardNumber(),
                                    creditStatusDTO.getCreditChannel(),
                                    Optional.ofNullable(status).map(CreditInquiryStatus::getDesc).orElse(message));
                            this.isCanceled = true;
                            applicationContext.publishEvent(new CreditQueryStopEvent(
                                    this, creditTaskDTO
                            ));
                            catT.setStatus(new XDecisionDataException(XDecisionDataExceptionType.DATA_CREDIT_REPORT_FETCH_ERROR));
                            initThreadLocal(requestId);
                        } else {
                            log.info("requsetId = {}, id_card_number = {},channel = {} 人行报告初始化或查询中，继续查询",
                                    requestId,
                                    creditStatusDTO.getIdCardNumber(),
                                    creditStatusDTO.getCreditChannel());
                        }
                    }
                }
            } catch (Exception e) {
                log.error("requestId = {}, id_card_number = {},channel = {} 获取人行变量异常",
                        requestId,
                        creditStatusDTO.getIdCardNumber(),
                        creditStatusDTO.getCreditChannel(),
                        e
                );
                Cat.logError(e);
                catT.setStatus(e);
                Cat.logEvent(VarCatTypeEnum.CREDIT_QUERY.getCode(),
                        VarCatTypeEnum.CREDIT_QUERY.getName(),
                        CatStatusEnum.EVENT_WARNING.getCode(),
                        "获取人行变量异常:" + StringUtil.getStackTrace(e));
            } finally {
                catT.complete();
                MDC.remove(VariableConst.TRACE_ID);
                MDC.remove(VariableConst.HANDLE_NAME);
                MDC.remove(VariableConst.COLLECT_STAGE);
                MDC.clear();
            }

        }

    }

    private static VariableApiRequest buildRequest(Set<String> varCodes, String requestId, CreditStatusDTO creditStatusDto) {
        VariableApiRequest request = new VariableApiRequest();
        request.setRequestId(requestId);
        request.setVarCodes(varCodes);
        request.setInputParams(buildInputParams(creditStatusDto));
        request.setVariableStrategyTypes(Collections.singletonList(VariableStrategyType.CREDIT));
        return request;
    }

    private static JSONObject buildInputParams(CreditStatusDTO creditStatusDto) {
        return new JSONObject()
                .fluentPut("app_id", creditStatusDto.getApplyId())
                .fluentPut("credit_channel", creditStatusDto.getCreditChannel())
                .fluentPut("name", creditStatusDto.getName())
                .fluentPut("stage", creditStatusDto.getStage())
                .fluentPut("inner_app", creditStatusDto.getInnerApp())
                .fluentPut("app", creditStatusDto.getApp())
                .fluentPut("mobile", creditStatusDto.getMobile())
                .fluentPut("id_card_number", creditStatusDto.getIdCardNumber())
                .fluentPut("type", creditStatusDto.getInputParams().getOrDefault("type", ""))
                .fluentPut("requester", creditStatusDto.getInputParams().getOrDefault("requester", ""))
                .fluentPut("biz_type", creditStatusDto.getInputParams().getOrDefault("biz_type", ""))
                .fluentPut("seq_id", creditStatusDto.getInputParams().getOrDefault("seq_id", ""))
                .fluentPut("source_type", creditStatusDto.getInputParams().getOrDefault("source_type", ""))
                ;

    }

    private static void setThreadLocal(String requestId) {
        Map<String, Integer> taskCountMap = TASK_COUNT.get();
        if (MapUtils.isNotEmpty(taskCountMap)) {
            taskCountMap.put(requestId, taskCountMap.getOrDefault(requestId, 0) + 1);
        } else {
            taskCountMap = new HashMap<>();
            taskCountMap.put(requestId, 0);

        }
        TASK_COUNT.set(taskCountMap);
    }

    private static void initThreadLocal(String requestId) {
        Map<String, Integer> taskCountMap = TASK_COUNT.get();
        if (MapUtils.isNotEmpty(taskCountMap)) {
            taskCountMap.put(requestId, 0);
        } else {
            taskCountMap = new HashMap<>();
            taskCountMap.put(requestId, 0);

        }
        TASK_COUNT.set(taskCountMap);
    }

    private static TransmittableThreadLocal getThreadLocal() {
        return TASK_COUNT;
    }

}
